package com.coze.jwt.server;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.io.IOUtils;

import com.coze.openapi.client.auth.OAuthConfig;
import com.coze.openapi.client.auth.OAuthToken;
import com.coze.openapi.service.auth.JWTOAuthClient;

import io.javalin.Javalin;
import io.javalin.http.staticfiles.Location;

/**
 * OAuth2令牌服务器，使用Javalin框架提供令牌获取和管理功能。
 * 依赖JWTOAuthClient处理OAuth认证流程，并通过静态资源提供用户界面。
 */
public class TokenServer {
  private final JWTOAuthClient oauthClient;
  private Javalin app;
  private final OAuthConfig appConfig;

  /**
   * 构造函数初始化OAuth客户端和应用配置。
   * @param oauthClient 处理OAuth认证的客户端实例
   * @param appConfig 应用配置信息
   */
  public TokenServer(JWTOAuthClient oauthClient, OAuthConfig appConfig) {
    this.oauthClient = oauthClient;
    this.appConfig = appConfig;
  }

  /**
   * 启动令牌服务器并配置路由和静态资源。
   * @param port 监听端口
   */
  public void start(int port) {
    app = Javalin.create(config -> {
        // 添加CORS配置解决跨域问题
        config.enableCorsForAllOrigins(); // 允许所有来源的CORS请求
        // 静态资源配置保持不变
        config.addStaticFiles(staticFiles -> {
            staticFiles.directory = "/assets";
            staticFiles.location = Location.CLASSPATH;
            staticFiles.hostedPath = "/assets";
        });
        config.addStaticFiles(staticFiles -> {
            staticFiles.directory = "/websites";
            staticFiles.location = Location.CLASSPATH;
        });
    })
    // 原有路由配置保持不变
    .get("/", ctx -> {
      // 渲染主页模板
      Map<String, String> model = new HashMap<>();
      model.put("client_type", appConfig.getClientType());
      model.put("client_id", appConfig.getClientId());
      String html = null;
      try {
        html = formatHtml(readFromResources("websites/index.html"), model);
      } catch (Exception e) {
        e.printStackTrace();
        throw e;
      }
      ctx.contentType("text/html");
      ctx.html(html);
    })
    .get("/callback", ctx -> {
      // 处理OAuth回调并获取访问令牌
      try {
        OAuthToken tokenResp = oauthClient.getAccessToken();
        ctx.sessionAttribute(genTokenSessionKey(), tokenResp);
        Map<String, String> model = new HashMap<>();
        model.put("token_type", tokenResp.getTokenType());
        model.put("access_token", tokenResp.getAccessToken());
        model.put("refresh_token", "");
        model.put(
            "expires_in",
            String.format(
                "%d (%s)",
                tokenResp.getExpiresIn(),
                timestampToDateTime(tokenResp.getExpiresIn())));
        if ("XMLHttpRequest".equals(ctx.req.getHeader("X-Requested-With"))) {
          ctx.json(model);
          return;
        }
        String html = formatHtml(readFromResources("websites/callback.html"), model);
        ctx.contentType("text/html");
        ctx.result(html);

      } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException("Authorization failed: " + e.getMessage());
      }
    })
    .get(
            "/token",
            ctx -> {
              // 处理OAuth回调并获取访问令牌
              try {
                OAuthToken tokenResp = oauthClient.getAccessToken(UUID.randomUUID().toString());
                ctx.sessionAttribute(genTokenSessionKey(), tokenResp);
                Map<String, String> model = new HashMap<>();
                model.put("token_type", tokenResp.getTokenType());
                model.put("access_token", tokenResp.getAccessToken());
                model.put("refresh_token", "");
                model.put(
                                    "expires_in",
                                    String.format(
                                            "%d (%s)",
                                            tokenResp.getExpiresIn(),
                                            timestampToDateTime(tokenResp.getExpiresIn())));
                            if ("XMLHttpRequest".equals(ctx.req.getHeader("X-Requested-With"))) {
                              ctx.json(model);
                              return;
                            }
                            String html = formatHtml(readFromResources("websites/callback.html"), model);
                            ctx.contentType("text/html");
                            ctx.result(html);

                          } catch (Exception e) {
                            e.printStackTrace();
                            throw new RuntimeException("Authorization failed: " + e.getMessage());
                          }
                        })
    .get(
                "/login",
                ctx -> {
                  // 重定向到回调端点触发OAuth流程
                  ctx.redirect("/callback");
                })
            .exception(
                Exception.class,
                (e, ctx) -> {
                  // 统一异常处理并渲染错误页面
                  Map<String, String> model = new HashMap<>();
                  model.put("error", e.getMessage());
                  String html = null;
                  try {
                    html = formatHtml(readFromResources("websites/error.html"), model);
                  } catch (Exception e1) {
                    ctx.status(500).result("Error getting html: " + e.getMessage());
                    return;
                  }
                  ctx.contentType("text/html");
                  ctx.result(html);
                })
            .start("127.0.0.1", port);
  }

  /**
   * 停止服务器实例。
   */
  public void stop() {
    if (app != null) {
      app.stop();
    }
  }

  /**
   * 将HTML模板中的占位符替换为实际值。
   * @param html 原始HTML内容
   * @param model 包含替换值的键值对
   * @return 替换后的HTML内容
   */
  private String formatHtml(String html, Map<String, String> model) {
    for (Map.Entry<String, String> entry : model.entrySet()) {
      html = html.replace("{{" + entry.getKey() + "}}", entry.getValue());
    }
    return html;
  }

  /**
   * 从类路径读取资源文件内容。
   * @param fileName 资源文件路径
   * @return 文件内容的UTF-8字符串
   */
  public String readFromResources(String fileName) {
    try {
      ClassLoader classLoader = getClass().getClassLoader();
      try (InputStream inputStream = classLoader.getResourceAsStream(fileName)) {
        if ((inputStream == null)) {
          throw new IllegalArgumentException("file not found: " + fileName);
        }
        return new String(IOUtils.toByteArray(inputStream), StandardCharsets.UTF_8);
      }
    } catch (IOException e) {
      throw new RuntimeException("read file failed: " + fileName, e);
    }
  }

  /**
   * 将Unix时间戳转换为本地时间字符串。
   * @param timestamp 秒级时间戳
   * @return 格式化后的日期时间字符串（yyyy-MM-dd HH:mm:ss）
   */
  public static String timestampToDateTime(long timestamp) {
    LocalDateTime dateTime =
        LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
    return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  }

  /**
   * 根据客户端ID生成会话键。
   * @return 会话键字符串
   */
  private String genTokenSessionKey() {
    return String.format("access_token_%s", appConfig.getClientId());
  }
}
